package com.smart.thrift.config.server;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.StrUtil;
import com.smart.thrift.annotation.ThriftServer;
import com.smart.thrift.annotation.ThriftService;
import com.smart.thrift.base.DefaultSerializer;
import com.smart.thrift.base.ThriftSerializer;
import com.smart.thrift.discoveryandbalance.discovery.AbstractRegistry;
import com.smart.thrift.exception.server.ThriftServerException;
import com.smart.thrift.model.common.ThriftServerProperties;
import com.smart.thrift.model.server.IThriftServer;
import com.smart.thrift.model.wrapper.ThriftServiceWrapper;
import com.smart.thrift.model.wrapper.ThriftServiceWrapperFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.transport.TTransportException;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Slf4j
@EnableConfigurationProperties(ThriftServerProperties.class)
public class ThriftServerAutoConfiguration implements ImportAware, ApplicationContextAware {

    private ApplicationContext applicationContext;

    private AnnotationAttributes annotationAttributes;

    @Override
    public void setImportMetadata(AnnotationMetadata metadata) {
        annotationAttributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(ThriftServer.class.getName()));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Bean
    @ConditionalOnMissingBean
    public ThriftSerializer defaultThriftSerializer() {
        return new DefaultSerializer();
    }

    @Bean
    @ConditionalOnMissingBean
    public ThriftServerManager thriftServerManager(ThriftServerProperties properties) throws NoSuchAlgorithmException {
        String[] beanNames = applicationContext.getBeanNamesForAnnotation(ThriftService.class);
        if (beanNames.length == 0) {
            log.error("Can't search any thrift service annotated with @ThriftMapper");
            throw new ThriftServerException("Can not found any thrift service");
        }
        String serverId = StrUtil.isNotBlank(annotationAttributes.getString("serverId")) ? annotationAttributes.getString("serverId") : properties.getServerId();
        String serverName = (StrUtil.isNotBlank(applicationContext.getApplicationName()) ? applicationContext.getApplicationName() : findBootClassName(applicationContext));
        List<ThriftServiceWrapper> serviceWrappers = Arrays.stream(beanNames).distinct().map(beanName -> {
            Object bean = applicationContext.getBean(beanName);
            Object target = bean;
            ThriftService thriftService = AnnotationUtil.getAnnotation(bean.getClass(), ThriftService.class);
            String serviceName = StrUtil.isBlank(thriftService.value()) ? beanName : thriftService.value();
            boolean match = Arrays.stream(target.getClass().getInterfaces()).anyMatch(clazz -> clazz.getName().endsWith("$Iface"));
            if (!match) {
                target = applicationContext.getBean(beanName + "$Iface");
            }
            return ThriftServiceWrapperFactory.wrapper(serviceName, bean.getClass().getSuperclass(), target, thriftService.version());
        }).collect(Collectors.toList());
        ThriftServerProperties.ThriftServerDiscoveryProperties discovery = properties.getDiscovery();
        AbstractRegistry registry = discovery.getEnabled() ? AbstractRegistry.getInstance(discovery.getDiscoveryType(), discovery.getHost(), discovery.getPort()) : null;

        ThriftServerManager thriftServerManager = new ThriftServerManager();
        thriftServerManager.setServerId(serverId);
        thriftServerManager.setServerName(serverName);
        thriftServerManager.setRegistry(registry);
        thriftServerManager.setProperties(properties);
        thriftServerManager.setServiceWrappers(serviceWrappers);
        return thriftServerManager;
    }

    private static String findBootClassName(ApplicationContext context) {
        Map<String, Object> annotatedBeans = context.getBeansWithAnnotation(SpringBootApplication.class);
        return annotatedBeans.isEmpty() ? null : ((Supplier<String>) () -> {
            String simpleName = annotatedBeans.values().toArray()[0].getClass().getSimpleName();
            if (simpleName.contains("$")) {
                simpleName = simpleName.substring(0, simpleName.indexOf('$'));
            }
            return simpleName.replace("Application", "");
        }).get();
    }

    @Bean
    @ConditionalOnMissingBean
    public ThriftServerGroup thriftServerGroup() throws TTransportException, IOException {
        Map<String, ThriftServerManager> beansOfType = applicationContext.getBeansOfType(ThriftServerManager.class);
        List<IThriftServer> serverList = new ArrayList<>();
        for (ThriftServerManager value : beansOfType.values()) {
            serverList.add(value.buildServer());
        }
        ThriftServerGroup thriftServerGroup = new ThriftServerGroup(serverList);
        String serverName = (StrUtil.isNotBlank(applicationContext.getApplicationName()) ? applicationContext.getApplicationName() : findBootClassName(applicationContext));
        thriftServerGroup.setServerName(serverName);
        return thriftServerGroup;
    }

    @Bean
    @ConditionalOnMissingBean
    public ThriftServerBootstrap thriftServerBootstrap(ThriftServerGroup thriftServerGroup) {
        return new ThriftServerBootstrap(thriftServerGroup);
    }
}
